# -*- coding: utf-8 -*-
# @Time    : 2024/6/25 15:34
# @Author  : yujiahao
# @File    : 13_panda_datetime.py
# @description:Pandas中的时间相关


# todo 1、 Pandas时间序列

"""
    顾名思义，时间序列（time series），就是由时间构成的序列，它指的是在一定时间内按照时间顺序测量的某个变量的取值序列。
    比如一天内的温度会随时间而发生变化，或者股票的价格会随着时间不断的波动，这里用到的一系列时间，就可以看做时间序列。

    时间序列包含三种应用场景，分别是：
        1. 特定的时刻（timestamp），也就是时间戳；
        2. 固定的日期（period），比如某年某月某日；
        3. 时间间隔（interval），每隔一段时间具有规律性；

    在处理时间序列的过程中，我们一般会遇到两个问题：
        1. 如何创建时间序列；
        2. 如何更改已生成时间序列的频率。

    Pandas 为解决上述问题提供了一套简单、易用的方法。

    下面用 Python 内置的 datetime 模块来获取当前时间，通过该模块提供的 now() 方法即可实现。

    示例代码：
        from datetime import datetime
        # 数据类型为 datetime
        print(datetime.now())

        输出结果：
        2020-12-16 16:36:18.791297
"""
import datetime

import numpy as np
import pandas as pd


def pandas_timestamp():
    # todo 1.1、创建时间戳

    '''TimeStamp（时间戳） 是时间序列中的最基本的数据类型，它将数值与时间点完美结合在一起。'''
    print(pd.Timestamp('2024-06-24 16:36:18'))

    '''同样，可以将整型或浮点型表示的时间转换为时间戳。默认的单位是纳秒(时间戳单位)'''
    print(pd.Timestamp(1587687255, unit='s'))

    # todo 1.2、创建时间范围
    '''
    通过 date_range() 方法可以创建某段连续的时间或者固定间隔的时间时间段。该函数提供了三个参数，分别是：
    start：开始时间
    end：结束时间
    freq：时间频率，默认为 "D"（天）
    '''

    # freq表示时间频率，每30min变化一次
    print(pd.date_range("9:00", "18:10", freq="30min").time)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 1.3、更改时间频率

    # 修改为按小时
    print(pd.date_range("6:10", "11:45", freq="h").time)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 1.4、转化为时间戳

    print(pd.to_datetime(pd.Series(['Jun 3, 2020', '2020-12-10', None]), format='mixed'))

    # 传入list，生成DatetimeIndex
    print(pd.to_datetime(['Jun 1, 2020', '2020-12-10', None], errors='coerce'))

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 1.5、频率和周期转换

    '''
    Time Periods 表示时间跨度，一段时间周期，它被定义在 Pandas Periods 类中，通过该类提供的方法可以实现将频率转换为周期。
    比如 Periods() 方法，可以将频率 "M"（月）转换为 Period（时间段）。

    下面示例，使用 asfreq() 和 start 参数，打印 "01" ，若使用 end 参数，则打印 "31"。
    '''

    x = pd.Period('2014', freq='M')
    # start参数
    print(x.asfreq('D', 'start'))
    # end参数
    print(x.asfreq('D', 'end'))

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    """
    Pandas 中常用的时间序列频率及其字符串别名（偏移量）：

    | 别名 | 描述                   | 别名 | 描述                   |
    |------|------------------------|------|---------------------|
    | B    | 工作日频率             | BQS  | 工作季度开始频率       |
    | D    | 日历日频率             | A    | 年终频率              |
    | W    | 每周频率               | BA   | 工作年度结束频率       |
    | M    | 月末频率               | BAS  | 工作年度开始频率       |
    | SM   | 半月结束频率            | BH   | 营业时间频率          |
    | BM   | 工作月结束频率          | H    | 小时频率              |
    | MS   | 月开始频率             | T,min| 每分钟频率             |
    | SMS  | 半月开始频率            | S    | 每秒钟频率            |
    | BMS  | 工作月开始频率          | L,ms | 毫秒                 |
    | Q    | 季末频率               | U,us | 微秒                 |
    | BQ   | 工作季度结束频率        | N    | 纳秒                 |
    | QS   | 季度开始频率           |      |                      |

    这些别名（偏移量）在 Pandas 中用于指定时间序列数据的频率。以下是一些示例代码，展示了如何使用这些频率别名：
    """

    # 示例：创建不同频率的时间序列

    # 工作日频率
    b_series = pd.date_range(start='2023-01-01', periods=5, freq='B')
    print("工作日频率:\n", b_series)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 日历日频率
    d_series = pd.date_range(start='2023-01-01', periods=5, freq='D')
    print("\n日历日频率:\n", d_series)

    # 每周频率
    w_series = pd.date_range(start='2023-01-01', periods=5, freq='W')
    print("\n每周频率:\n", w_series)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 月末频率
    m_series = pd.date_range(start='2023-01-01', periods=5, freq='ME')
    print("\n月末频率:\n", m_series)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 小时频率
    h_series = pd.date_range(start='2023-01-01', periods=5, freq='h')
    print("\n小时频率:\n", h_series)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 每分钟频率
    t_series = pd.date_range(start='2023-01-01', periods=5, freq='min')
    print("\n每分钟频率:\n", t_series)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 每秒钟频率
    s_series = pd.date_range(start='2023-01-01', periods=5, freq='s')
    print("\n每秒钟频率:\n", s_series)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 创建一个表示2024年的Period对象
    period_year = pd.Period('2024', freq='Y')
    print(period_year)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    print(pd.date_range(start='2023-01-01', end='2023-02-01', freq='ME'))

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 1.6、时间周期计算

    '''周期计算，指的是对时间周期进行算术运算，所有的操作将在“频率”的基础上执行。'''

    # S表示秒
    x = pd.Period('2014', freq='s')
    print(x)
    # 加1s的时间
    print(x + 1)

    # 定义时期period，默认freq="Y"年份，可以直接减
    p1 = pd.Period('2020')
    p2 = pd.Period('2019')
    # 使用f''格式化输出
    print(f'p1={p1}年')
    print(f'p2={p2}年')
    print(f'p1和p2间隔{p1 - p2}年')
    # f''表示字符串格式化输出
    print(f'五年前是{p1 - 5}年')

    # todo 1.7、创建时间周期
    '''我们可以使用 period_range() 方法来创建时间周期范围。'''
    # Y表示年
    p = pd.period_range('2016', '2018', freq='Y')
    print(p)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 1.8、时间序列转换
    '''如果想要把字符串日期转换为 Period，首先需要将字符串转换为日期格式，然后再将日期转换为 Period'''
    # 创建时间序列
    index = pd.date_range("2020-03-17", "2020-03-30", freq="1.5h")
    # 随机选取4个互不相同的数
    loc = np.random.choice(np.arange(len(index)), size=4, replace=False)
    loc.sort()
    ts_index = index[loc]
    print(ts_index)
    pd_index = ts_index.to_period('D')
    print(pd_index)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 1.9、 创建日期范围
    '''
    pd.date_range 生成的是具体的时间点序列，适用于处理精确到具体时刻的数据。在这个例子中，我们生成了每个月的最后一天。
    pd.period_range 生成的是时间段序列，适用于处理时间段数据。在这个例子中，我们生成了每个月的整个时间段。
    选择使用哪一个取决于你的数据分析需求：如果你需要处理具体的时间点数据，使用 DatetimeIndex；如果你需要处理时间段数据，使用 PeriodIndex。
    '''

    print(pd.date_range('12/15/2020', periods=10, freq='ME'))
    print(pd.period_range('12/15/2020', periods=10, freq='M'))

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 1.10、 工作日时间
    '''bdate_range() 表示创建工作日的日期范围，它与 date_range() 不同，它不包括周六、周日。
        date_range() 默认频率是日历日，而 bdate_range() 的默认频率是工作日。'''
    print(pd.bdate_range('11/25/2020', periods=8))

    # todo 2、Pandas日期时间格式化


def pandas_dateformat():
    '''当进行数据分析时，我们会遇到很多带有日期、时间格式的数据集，在处理这些数据集时，可能会遇到日期格式不统一的问题，
    此时就需要对日期时间做统一的格式化处理。比如“Wednesday, June 6, 2020”可以写成“6/6/20”，或者写成“06-06-2020。
    【日期格式化符号】

        符号    说明
        %y      两位数的年份表示（00-99）
        %Y      四位数的年份表示（000-9999）
        %m      月份（01-12）
        %d      月内中的一天（01-31）
        %H      24小时制小时数（00-23）
        %I      12小时制小时数（01-12）
        %M      分钟数（00-59）
        %S      秒（00-59）
        %a      本地英文缩写星期名称
        %A      本地英文完整星期名称
        %b      本地缩写英文的月份名称
        %B      本地完整英文的月份名称
        %w      星期（0-6），星期天为星期的开始
        %W      一年中的星期数（00-53），星期一为星期的开始
        %x      本地相应的日期表示
        %X      本地相应的时间表示
        %Z      当前时区的名称
        %U      一年中的星期数（00-53），星期天为星期的开始
        %j      年内的一天（001-366）
        %c      本地相应的日期表示和时间表示



    '''

    # todo 2.1、python处理

    '''注意：strftime() 可以将 datetime 类型转换为字符串类型，恰好与 strptime() 相反。'''

    # 获取当前时间
    now = datetime.datetime.now()

    # 格式化输出示例
    formatted_date = now.strftime("%Y-%m-%d %H:%M:%S")
    print("当前日期和时间：", formatted_date)

    # 各个格式化符号的示例
    print("两位数年份：", now.strftime("%y"))
    print("四位数年份：", now.strftime("%Y"))
    print("月份：", now.strftime("%m"))
    print("月内的一天：", now.strftime("%d"))
    print("24小时制小时数：", now.strftime("%H"))
    print("12小时制小时数：", now.strftime("%I"))
    print("分钟数：", now.strftime("%M"))
    print("秒数：", now.strftime("%S"))
    print("本地英文缩写星期名称：", now.strftime("%a"))
    print("本地英文完整星期名称：", now.strftime("%A"))
    print("本地缩写英文的月份名称：", now.strftime("%b"))
    print("本地完整英文的月份名称：", now.strftime("%B"))
    print("星期（0-6）：", now.strftime("%w"))
    print("一年中的星期数（星期一为开始）：", now.strftime("%W"))
    print("本地相应的日期表示：", now.strftime("%x"))
    print("本地相应的时间表示：", now.strftime("%X"))
    print("当前时区的名称：", now.strftime("%Z"))
    print("一年中的星期数（星期天为开始）：", now.strftime("%U"))
    print("年内的一天：", now.strftime("%j"))
    print("本地相应的日期和时间表示：", now.strftime("%c"))

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 2.2、Pandas处理

    """
    除了使用 Python 内置的 strptime() 方法外，你还可以使用 Pandas 模块的 pd.to_datetime() 和 pd.DatetimeIndex() 进行转换
    DatetimeIndex 和 to_datetime 是 Pandas 中处理时间序列数据的两个重要工具。
    它们在不同的场景下使用，但都与日期和时间的处理密切相关。
    """

    # ============================
    # DatetimeIndex
    # ============================

    """
    DatetimeIndex 是 Pandas 中的一种索引类型，专门用于处理时间序列数据。
    它是 Index 的子类，专门用于存储和操作时间戳。
    """

    # 创建 DatetimeIndex 的多种方式

    # 1. 使用 pd.date_range 创建连续的时间序列
    print("创建连续的时间序列:")
    date_range = pd.date_range(start='2023-01-01', end='2023-01-10')
    print("日期范围:\n", date_range)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 2. 从现有的日期时间列表创建
    print("从日期时间列表创建 DatetimeIndex:")
    dates = ['2023-01-01', '2023-01-02', '2023-01-03']
    datetime_index = pd.DatetimeIndex(dates)
    print("从列表创建的 DatetimeIndex:\n", datetime_index)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 3. 从 pd.to_datetime 的输出创建
    print("从 pd.to_datetime 的输出创建 DatetimeIndex:")
    dates = ['2023-01-01', '2023-01-02', '2023-01-03']
    datetime_index = pd.to_datetime(dates)
    print("从 to_datetime 创建的 DatetimeIndex:\n", datetime_index)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # ============================
    # to_datetime
    # ============================

    """
    pd.to_datetime 是一个函数，用于将各种格式的日期时间数据转换为 datetime64 类型的数据。
    它可以处理字符串、数字、列表、数组等多种输入类型，并返回 DatetimeIndex 或 Timestamp 对象。
    """

    # 使用 pd.to_datetime 的多种方式

    # 1. 将日期时间字符串转换为 Timestamp
    print("将日期时间字符串转换为 Timestamp:")
    date_str = '2023-01-01'
    timestamp = pd.to_datetime(date_str)
    print("Timestamp:\n", timestamp)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 2. 将日期时间字符串列表转换为 DatetimeIndex
    print("将日期时间字符串列表转换为 DatetimeIndex:")
    dates = ['2023-01-01', '2023-01-02', '2023-01-03']
    datetime_index = pd.to_datetime(dates)
    print("从列表转换的 DatetimeIndex:\n", datetime_index)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 3. 处理包含缺失值的日期时间数据
    print("处理包含缺失值的日期时间数据:")
    dates = ['2023-01-01', 'not a date', '2023-01-03']
    datetime_index = pd.to_datetime(dates, errors='coerce')
    print("处理后的 DatetimeIndex（包含缺失值）:\n", datetime_index)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # ============================
    # 区别和联系
    # ============================

    """
    - DatetimeIndex 是一个索引类型，用于在 Pandas 数据结构（如 Series 和 DataFrame）中处理时间序列数据。
    - pd.to_datetime 是一个函数，用于将各种格式的日期时间数据转换为 datetime64 类型的数据，通常返回 DatetimeIndex 或 Timestamp 对象。

    两者的联系在于，pd.to_datetime 常常用于创建 DatetimeIndex，从而在 Pandas 数据结构中方便地处理和操作时间序列数据。
    """

    # ============================
    # 示例结合
    # ============================

    print("示例结合:")
    # 定义一个包含日期时间字符串的列表
    date = ['2012-05-06 11:00:00', '2012-05-16 11:00:00']

    # 使用 pd.to_datetime 将日期时间字符串转换为 DatetimeIndex
    pd_date = pd.to_datetime(date)

    # 生成一个包含两个随机数的 Pandas Series，索引为 pd_date
    df = pd.Series(np.random.randn(2), index=pd_date)

    print("带有 DatetimeIndex 的 Series:\n", df)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 通过 to_datetime() 直接转换为 datetime 类型
    date = ['2012-05-06 11:00:00', '2012-05-16 11:00:00']
    pd_date = pd.to_datetime(date)
    df = pd.Series(np.random.randn(2), index=pd_date)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 使用 Datetimeindex() 函数设置时间序，示例如下：
    date = pd.DatetimeIndex(['1/1/2008', '1/2/2008', '1/3/2008', '1/4/2008', '1/5/2008'])
    dt = pd.Series(np.random.randn(5), index=date)
    print(dt)

    # todo 3、Padans Timedelta时间差


def pandas_timedelta():
    '''Timedelta 表示时间差（或者时间增量），我们可以使用不同的时间单位来表示它，比如，天、小时、分、秒。时间差的最终的结果可以是正时间差，也可以是负时间差。

    主要介绍创建 Timedelta （时间差）的方法以及与时间差相关的运算法则。
    '''

    # todo 3.1 字符串
    '''通过传递字符串可以创建 Timedelta 对象.     5 days 08:06:59'''
    print(pd.Timedelta('5 days 8 hours 6 minutes 59 seconds'))
    # todo 3.2 整数
    '''通过传递整数值和unit参数也可以创建一个 Timedelta 对象。  0 days 19:00:00'''
    print(pd.Timedelta(19, unit='h'))
    # todo 3.3 数据偏移量
    '''数据偏移量， 比如，周(weeks)、天(days)、小时(hours)、分钟(minutes)、秒(milliseconds)、毫秒、微秒、纳秒都可以使用。 2 days 06:00:00'''
    print(pd.Timedelta(days=2, hours=6))
    # todo 3.4 to_timedelta()
    '''可以使用pd.to_timedelta()方法，将具有 timedelta 格式的值 (标量、数组、列表或 Series）转换为 Timedelta 类型。
        如果输入是 Series，则返回 Series；
        如果输入是标量，则返回值也为标量，其他情况输出 TimedeltaIndex。
        
        TimedeltaIndex(['1 days 06:05:01.000030', '0 days 00:00:00.000015500', NaT], dtype='timedelta64[ns]', freq=None)
        TimedeltaIndex(['0 days 00:00:00', '0 days 00:00:01', '0 days 00:00:02',
                '0 days 00:00:03', '0 days 00:00:04'],
               dtype='timedelta64[ns]', freq=None)
        '''
    print(pd.to_timedelta(['1 days 06:05:01.00003', '15.5us', 'nan']))
    print(pd.to_timedelta(np.arange(5), unit='s'))

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 3.5 算术操作
    '''通过对datetime64[ns]类型的时间序列或时间戳做算术运算，其运算结果依然是datetime64[ns]数据类型。
    建一个带有 Timedelta 与 datetime 的 DataFrame 对象，并对其做一些算术运算。
    
               A      B
    0 2020-01-01 0 days
    1 2020-01-02 1 days
    2 2020-01-03 2 days
    3 2020-01-04 3 days
    4 2020-01-05 4 days

    
    '''
    s = pd.Series(pd.date_range('2020-1-1', periods=5, freq='D'))
    # 推导式用法
    td = pd.Series([pd.Timedelta(days=i) for i in range(5)])
    df = pd.DataFrame(dict(A=s, B=td))
    print(df)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 3.6 加法运算
    '''
               A      B          C
    0 2020-01-01 0 days 2020-01-01
    1 2020-01-02 1 days 2020-01-03
    2 2020-01-03 2 days 2020-01-05

    
    '''

    s = pd.Series(pd.date_range('2020-1-1', periods=3, freq='D'))
    td = pd.Series([pd.Timedelta(days=i) for i in range(3)])
    df = pd.DataFrame(dict(A=s, B=td))
    # 加法运算
    df['C'] = df['A'] + df['B']
    print(df)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 3.7 减法运算
    '''
                   A      B          C          D
        0 2012-01-01 0 days 2012-01-01 2012-01-01
        1 2012-01-02 1 days 2012-01-03 2012-01-02
        2 2012-01-03 2 days 2012-01-05 2012-01-03
    '''

    s = pd.Series(pd.date_range('2012-1-1', periods=3, freq='D'))
    td = pd.Series([pd.Timedelta(days=i) for i in range(3)])
    df = pd.DataFrame(dict(A=s, B=td))
    df['C'] = df['A'] + df['B']
    df['D'] = df['C'] - df['B']
    print(df)


def main():
    # pandas_timestamp()
    # pandas_dateformat()
    pandas_timedelta()


if __name__ == '__main__':
    main()
